package com.atguigu.gmall.gateway.filter;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import com.atguigu.gmall.common.util.IpUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.net.URI;

/**
 * @author tongatong
 * @name AuthGlobalFilter
 * @description
 * 该拦截器的作用：
 *  1. 防止用户直接访问内网链接
 *  2. 当用户进行异步请求访问URL的时候，需要登录
 *  3. 访问特定的网页，重定向，进行跳转
 *
 * @date 2021/9/23
 * @time 10:25
 */
@Component
public class AuthGlobalFilter implements GlobalFilter {
    private AntPathMatcher antPathMatcher = new AntPathMatcher();
    @Resource
    private RedisTemplate<String, String> redisTemplate;
    @Value("${authUrls.url}")
    public String authUrls;  // trade.html,myOrder.html,list.html

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //请求对象
        ServerHttpRequest request = exchange.getRequest();
        //响应对象
        ServerHttpResponse response = exchange.getResponse();
        //获取URI, 这里的URI带域名例如：www.gmall.com/api/hello
        URI uri = request.getURI();
        //获取去除域名的路径部分,格式：/api/hello
        String path = uri.getPath();
        //查看URL是否符合要过滤的规则，规则：/**/inner/**
        //true:匹配，返回错误信息；false：不匹配

        //   /**/inner/**
        // 内网链接，不能够直接访问，必须通过网关
        if (antPathMatcher.match("/**/inner/**", path)) {
            //提示没有访问权限
            return out(response, ResultCodeEnum.PERMISSION);
        }
        //用户未登录，请求拦截
        //当从cookie，或者请求头中获取不到userID的时候，说明用户未登录
        //用户id从请求头中可以获取
        String userId = getUserID(request);
        //获取用户的临时Id
        String userTempId = getUserTempId(request);
        //-1，说明token被盗用，则立即停止程序
        if ("-1".equals(userId)){
            //  停止运行 没有权限，提示没有访问权限
            return out(response, ResultCodeEnum.PERMISSION);
        }
        //  /api/**/auth/**
        //某些URL只有在登录状态，才能访问
        if (antPathMatcher.match("/api/**/auth/**", path))
            //用户id不为null，才说明在登录状态
            if (StringUtils.isEmpty(userId))
                //提供报错信息,并提示用户未登录
                return out(response,ResultCodeEnum.LOGIN_AUTH);

        //登录跳转，当访问到哪些网页的时候，会跳转到登录页面
        //数据格式： trade.html,myOrder.html,list.html
        String[] split = authUrls.split(",");
        //当path中包含这些字符串的时候，跳转到登录页面
        for (String url : split) {
            //路径中存在要登录的页面，并且用户id为空，没有登录
            if (path.contains(url) && StringUtils.isEmpty(userId)) {
                //  提示，跳转到登录！
                //  设置一些响应的参数！
                response.setStatusCode(HttpStatus.SEE_OTHER);
                //  设置请求头 ：
                response.getHeaders().set(HttpHeaders.LOCATION,"http://www.gmall.com/login.html?originUrl="+request.getURI());
                //  代表重定向！
                return response.setComplete();
            }
        }

        //将用户id放入请求头中
        if (!StringUtils.isEmpty(userId) || !StringUtils.isEmpty(userTempId)) {
            if (!StringUtils.isEmpty(userId))
                request.mutate().header("userId", userId).build();
            if (!StringUtils.isEmpty(userTempId))
                request.mutate().header("userTempId", userTempId).build();
            //返回处理之后的exchange
            return chain.filter(exchange.mutate().request(request).build());
        }

        return chain.filter(exchange);
    }

    //获取临时用户id
    private String getUserTempId(ServerHttpRequest request) {
        //临时用户id，可能存在cookie中，也有可能存储在请求头中
        String userTempId;
        HttpCookie userTempIdCookie = request.getCookies().getFirst("userTempId");
        if (null != userTempIdCookie)
            userTempId = userTempIdCookie.getValue();
        else
            userTempId = request.getHeaders().getFirst("userTempId");
        return userTempId;
    }

    //获取用户id
    private String getUserID(ServerHttpRequest request) {
        //获取userID的方式有两种：一种是从cookie中获取token的值，然后从token中获取用户id，第二种是从请求头中获取
        //而用户id存储在token中，因此需要先获取到token的值
        String token;
        //这里获取不到，返回null
        HttpCookie tokenCookie = request.getCookies().getFirst("token");
        //cookie中存在token
        if (null != tokenCookie) {
            //获取不到会返回空字符串，不会返回null
            token = tokenCookie.getValue();
        }else {
            //从请求头中获取token, 获取不到，返回null
            token = request.getHeaders().getFirst("token");
        }
        if (!StringUtils.isEmpty(token)) {
            //redis中的key值
            String userKey = "user:login:"+token;
            //从redis中获取值, 当获取不到的时候，返回null
            String strJson = redisTemplate.boundValueOps(userKey).get();
            //从redis中获取到的是json格式的对象，需要转化成java对象
            JSONObject jsonObject = JSONObject.parseObject(strJson);
            //获取ip，这里防止盗取token
            String ip = (String) jsonObject.get("ip");
            //判断登录ip和cookie中记录的ip是否一致
            if (IpUtil.getGatwayIpAddress(request).equals(ip)) {
                //ip地址一致，获取用户id，并返回
                return (String) jsonObject.get("userId");
            }else {
                //ip地址不匹配，返回错误值 -1 。
                return "-1";
            }
        }
        return null;
    }

    //用户提示信息输入
    private Mono<Void> out(ServerHttpResponse response, ResultCodeEnum resultCodeEnum) {
        //输入的内容在 resultCodeEnum中！ 如果有字符的，可以设置一下字符集！
        Result<String> result = Result.build(null, resultCodeEnum);
        String str = JSON.toJSONString(result);
        DataBuffer wrap = response.bufferFactory().wrap(str.getBytes());
        //设置一下请求头，还有字符集
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        return response.writeWith(Mono.just(wrap));
    }
}
